package cn.car.biz.carAccess.impl;

import cn.car.biz.carAccess.PlateDetectedBiz;
import cn.car.biz.carAccess.cache.VehicleLastPlateDetectCache;
import cn.car.biz.carAccess.cache.VehiclePlateResultCache;
import cn.car.biz.carAccess.event.GIoInResultEvent;
import cn.car.biz.carAccess.event.PlateDetectResultEvent;
import cn.car.biz.carAccess.support.PlateDetectedSupport;
import cn.car.biz.carAccess.support.PlateDetectedSupportFactory;
import cn.car.domains.ctx.PlateDetectedContext;
import cn.car.domains.entity.CameraInfoEntity;
import cn.car.domains.entity.VehicleCommonInfoEntity;
import cn.car.domains.enums.CameraWorkModeEnum;
import cn.car.domains.enums.GIoInTypeEnum;
import cn.car.domains.enums.InoutTypeEnum;
import cn.car.domains.enums.VehicleTypeEnum;
import cn.car.domains.info.*;
import cn.car.exception.CarAccessControlException;
import cn.car.service.CameraService;
import cn.car.service.CameraSnapshotService;
import cn.car.service.VehicleCommonInfoService;
import cn.car.service.config.SystemConfig;
import cn.car.utils.SemaphoreUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.Map;

@Service
@Slf4j
public class PlateDetectedBizImpl implements PlateDetectedBiz {

    @Resource
    private SystemConfig systemConfig;

    @Resource
    private VehicleCommonInfoService vehicleCommonInfoService;

    @Resource
    private CameraService cameraService;

    @Resource
    private PlateDetectedSupportFactory factory;

    @Resource
    private ApplicationEventPublisher publisher;

    @Resource
    private CameraSnapshotService cameraSnapshotService;

    @Resource
    private VehiclePlateResultCache vehiclePlateResultCache;

    @Resource
    private VehicleLastPlateDetectCache vehicleLastPlateDetectCache;

    /**
     * 识别车辆逻辑
     */
    @Override
    public Boolean receiveCarDetectedRequest(PlateDetectedInternalInfo requestInfo) {

        /*
         * 构建上下文对象
         */
        PlateDetectedContext plateDetectedContext = new PlateDetectedContext();
        BeanUtils.copyProperties(requestInfo, plateDetectedContext);


        //前置校验 过滤忽略车牌
        String plateNumber = requestInfo.getVehicleInfo().getPlateNumber();
        try {
            checkPlateNumber(plateNumber);
        } catch (Exception e) {
            return false;
        }

        //转化ip。一组ip对应一个ip
        convertCameraIp(requestInfo);

        InternalCameraInfo internalCameraInfo = requestInfo.getCameraInfo();


        //获取相机
        CameraInfoEntity cameraInfoEntity = cameraService.getCameraInfoByIp(internalCameraInfo.getIp());
        plateDetectedContext.setCameraInfoEntity(cameraInfoEntity);


        //设置出入口
        plateDetectedContext.setInOutType(cameraInfoEntity.getInOutType());

        try {
            doReceiveCarDetectedRequest(plateDetectedContext);
            plateDetectedContext.setIsOpen(true);
            return true;
        } catch (CarAccessControlException e) {
            if (e.getDefineCode().equals(CarAccessControlException.LEDGER_RECORD_ERROR.getDefineCode())) {
                //台账记录异常，报警
                log.error("[GATE_HANDLER] ledger record error, need alert, plateNumber: {}, cameraIp: {}"
                        , plateDetectedContext.getVehicleInfo().getPlateNumber()
                        , plateDetectedContext.getCameraInfo().getIp(), e);
                plateDetectedContext.setIsOpen(true);
                return true;
            } else {
                plateDetectedContext.setIsOpen(false);
                return false;
            }
        } catch (Exception e) {
            log.error("[GATE_HANDLER] error", e);
            plateDetectedContext.setIsOpen(false);
            return false;
        } finally {
            //发消息
            PlateDetectResultEvent plateDetectResultEvent = new PlateDetectResultEvent(this, plateDetectedContext);
            publisher.publishEvent(plateDetectResultEvent);
        }


    }

    @Override
    public void receiveHeartbeat(InternalHeartDataInfo internalHeartDataInfo) {

    }

    @Override
    public void snapshotStorage(InternalSnapshotInfo requestInternal) {
        String ip = requestInternal.getIp();
        CameraInfoEntity cameraInfoEntity = cameraService.getCameraInfoByIp(requestInternal.getIp());
        if (cameraInfoEntity == null || !CameraWorkModeEnum.SNAP.name().equals(cameraInfoEntity.getWorkMode())){
            return;
        }
        String imageFile = requestInternal.getImageFile();
        cameraSnapshotService.storeImage(ip, imageFile);
    }



    private void convertCameraIp(PlateDetectedInternalInfo requestInfo) {
        InternalCameraInfo internalCameraInfo = requestInfo.getCameraInfo();
        Map<String, String> detectedCameraMap = systemConfig.getDetectedCameraMap().get();
        Map<String, String> bindCameraMap = systemConfig.getBindCameraMap().get();
        if (StrUtil.isNotEmpty(detectedCameraMap.get(internalCameraInfo.getIp()))) {

            VehicleCommonInfoEntity vehicleCommonInfo = vehicleCommonInfoService.getVehicleByNumber(requestInfo.getVehicleInfo().getPlateNumber());
            if (vehicleCommonInfo == null ) {
                // 未找到对应的车辆信息,正常，车辆未注册
                log.info("[GATE_HANDLER] could not find vehicle info, 未找到车辆信息, plateNumber: {}", requestInfo.getVehicleInfo().getPlateNumber());
                throw CarAccessControlException.DATE_NOT_EXIST.newInstance("未找到车辆信息");
            }

            if (!VehicleTypeEnum.ENTRY_EXIT.name().equals(vehicleCommonInfo.getVehicleType())){
                log.info("[GATE_HANDLER] 辅助摄像头只识别运输车辆, plateNumber: {}", requestInfo.getVehicleInfo().getPlateNumber());
                throw CarAccessControlException.SYSTEM_ERROR.newInstance("辅助摄像头只识别运输车辆");
            }

            log.info("[GATE_HANDLER] 转发辅助摄像头请求, ip: {}, forwardIp:{}", internalCameraInfo.getIp(), detectedCameraMap.get(internalCameraInfo.getIp()));
            internalCameraInfo.setIp(detectedCameraMap.get(internalCameraInfo.getIp()));
        }

        if (StrUtil.isNotEmpty(bindCameraMap.get(internalCameraInfo.getIp()))) {
            log.info("[GATE_HANDLER] 转发绑定摄像头请求, ip: {}, forwardIp:{}", internalCameraInfo.getIp(), bindCameraMap.get(internalCameraInfo.getIp()));
            internalCameraInfo.setIp(bindCameraMap.get(internalCameraInfo.getIp()));
        }
    }

    private void checkPlateNumber(String plateNumber) {
        String ignorePlateNumberRegular = systemConfig.getIgnorePlateNumber().get();
        if (plateNumber.matches(ignorePlateNumberRegular)) {
            log.info("[GATE_HANDLER] plateNumber is ignore ignorePlateNumberRegular, plateNumber:{}", plateNumber);
            throw CarAccessControlException.SYSTEM_ERROR.newInstance("plateNumber is ignore plateNumber");
        }
    }


    private void doReceiveCarDetectedRequest(PlateDetectedContext plateDetectedContext) throws InterruptedException {


        //Lock, redis, 唯一索引，zk
        SemaphoreUtil.tryAcquire(plateDetectedContext.getCameraInfo().getIp());


        //校验车辆是否在一段事件内不能识别
        VehicleLastPlateDetectInfo vehicleLastPlateDetectInfo = vehicleLastPlateDetectCache.get(plateDetectedContext.getVehicleInfo().getPlateNumber());
        if (vehicleLastPlateDetectInfo != null) {
            DateTime offset = DateUtil.offset(vehicleLastPlateDetectInfo.getLastPlateDetectedTime(), DateField.SECOND, Integer.parseInt(systemConfig.getLastDetectBetweenTime().get()));
            if (offset.after(new Date())) {
                throw CarAccessControlException.SYSTEM_ERROR.newInstance("车辆在一段时间内不能识别");
            }
        }


        //校验车牌
        checkVehicleNumber(plateDetectedContext);

        //选择实现类 - 工厂
        PlateDetectedSupport support = factory.getSupport(plateDetectedContext.getVehicleCommonInfoEntity().getVehicleType(), plateDetectedContext.getInOutType());

        //判断是否可以放行
        if (InoutTypeEnum.in.name().equals(plateDetectedContext.getInOutType())) {
            support.processIn(plateDetectedContext);
        } else if (InoutTypeEnum.out.name().equals(plateDetectedContext.getInOutType())) {
            support.processOut(plateDetectedContext);
        } else {
            log.error("[GATE_HANDLER] invalid inoutType, inoutType: {}", plateDetectedContext.getInOutType());
            throw CarAccessControlException.SYSTEM_ERROR.newInstance("无效的进出场类型");
        }


        //判断是否重复识别 不管多少次识别 只返回一次ok
        PlateDetectedContext plateCache = vehiclePlateResultCache.get(plateDetectedContext.getCameraInfo().getIp());
        if (plateCache != null
            && plateDetectedContext.getVehicleInfo().getPlateNumber().equals(plateCache.getVehicleInfo().getPlateNumber())
            && plateCache.getIsOpen()) {
                throw CarAccessControlException.SYSTEM_ERROR.newInstance("重复识别，暂不处理");

        }

        //台账记录
        try {
            if (InoutTypeEnum.in.name().equals(plateDetectedContext.getInOutType())) {
                support.recordIn(plateDetectedContext);
            } else if (InoutTypeEnum.out.name().equals(plateDetectedContext.getInOutType())) {
                support.recordOut(plateDetectedContext);
            } else {
                log.error("[GATE_HANDLER] invalid inoutType, inoutType: {}", plateDetectedContext.getInOutType());
                throw CarAccessControlException.SYSTEM_ERROR.newInstance("无效的进出场类型");
            }
        } catch (Exception e) {
            log.error("台账记录异常", e);
            throw CarAccessControlException.LEDGER_RECORD_ERROR;
        }


    }

    private void checkVehicleNumber(PlateDetectedContext plateDetectedContext) {

        //校验车牌
        VehicleCommonInfoEntity vehicleCommonInfoEntity = vehicleCommonInfoService.getVehicleByNumber(plateDetectedContext.getVehicleInfo().getPlateNumber());

        if (vehicleCommonInfoEntity == null && plateDetectedContext.getInOutType().equals(InoutTypeEnum.in.name())) {
            throw CarAccessControlException.DATE_NOT_EXIST.newInstance("车辆不存在");
        }

        plateDetectedContext.setVehicleCommonInfoEntity(vehicleCommonInfoEntity);

    }


    @Override
    public void gIOIn(InternalGIoInInfo internalGIoInInfo) {


        Integer openValue = systemConfig.getGateSignalOpen().get();
        Integer source = systemConfig.getGateSignalSource().get();

        if (!source.equals(internalGIoInInfo.getSource())) {
            return;
        }



        //获取上一次的缓存记录
        PlateDetectedContext plateDetectedContext = vehiclePlateResultCache.get(internalGIoInInfo.getIp());
        if (plateDetectedContext == null) {
            log.error("抬杆/落杆 未获取到缓存记录");
            return;
        }

        CameraInfoEntity cameraInfoEntity = plateDetectedContext.getCameraInfoEntity();
        if (!internalGIoInInfo.getIp().equals(cameraInfoEntity.getCameraIp())) {
            log.info("[doGIOIn] 接受到非识别相机gIoIn信号不处理, ip: {}", internalGIoInInfo.getIp());
            return;
        }

        if (openValue.equals(internalGIoInInfo.getValue())) {
            plateDetectedContext.setUpTime(new Date());
        } else {
            plateDetectedContext.setDownTime(new Date());
        }


        if (!plateDetectedContext.getIsOpen()) {
            //做一些手动开门的处理 记录台账

            //查一下会更好
            VehicleCommonInfoEntity vehicleCommonInfoEntity = vehicleCommonInfoService.getVehicleByNumber(plateDetectedContext.getVehicleInfo().getPlateNumber());

            plateDetectedContext.setVehicleCommonInfoEntity(vehicleCommonInfoEntity);


            //选择实现类 - 工厂
            PlateDetectedSupport support = factory.getSupport(vehicleCommonInfoEntity == null ? null :plateDetectedContext.getVehicleCommonInfoEntity().getVehicleType(), plateDetectedContext.getInOutType());

            if (plateDetectedContext.getInOutType().equals(InoutTypeEnum.in.name())) {
                support.manualRecordIn(plateDetectedContext);
            } else if (InoutTypeEnum.out.name().equals(plateDetectedContext.getInOutType())) {
                support.manualRecordOut(plateDetectedContext);
            } else {
                log.error("[GATE_HANDLER] invalid inoutType, inoutType: {}", plateDetectedContext.getInOutType());
                throw CarAccessControlException.SYSTEM_ERROR.newInstance("无效的进出场类型");
            }

        }


        plateDetectedContext.setGIoInType(openValue.equals(internalGIoInInfo.getValue()) ? GIoInTypeEnum.up.name() : GIoInTypeEnum.down.name());



        //发消息
        GIoInResultEvent gIoInResultEvent = new GIoInResultEvent(this, plateDetectedContext);
        publisher.publishEvent(gIoInResultEvent);


    }
}
